#include "WidgetMovimiento.h"
#include "../common/utils/FileChecker.h"
#include "../common/lib/SDL_rotozoom.h"
#include "widgets/PlayScenarioPanel.h"

#define COLISION_WINDOW 1 //Tiene que ser MAYOR A UNO!!
#define COLISION_RECTANGLE_WINDOW 1 //Tiene que ser MAYOR A UNO!!
#define DELTA_PROYECTEDMIN 50

#define Maximum(a, b) ((a > b) ? a : b)
#define Minimum(a, b) ((a < b) ? a : b)

void WidgetMovimiento::initWidgetMovimiento(playElement type, int damage, int posX, int posY, int angle)
{
    this->type = type;
    this->angle = angle;
    nextAngle = angle;
    this->damage = damage;
    surfaceShown = NULL;
    drawable = true;
    nextX = posX;
    nextY = posY;
    prepareSurfaceShown();
}

WidgetMovimiento::WidgetMovimiento(playElement type,int posX, int posY, std::string imagePath, Widget *parent, int damage)
:Widget(posX, posY,imagePath, parent) {
    initWidgetMovimiento(type, damage, posX, posY);
}

WidgetMovimiento::WidgetMovimiento(playElement type,int posX, int posY,int angle, std::string imagePath, Widget *parent, int damage)
:Widget(posX, posY,imagePath, parent) {
    initWidgetMovimiento(type, damage, posX, posY,angle);
}


WidgetMovimiento::WidgetMovimiento(playElement type,int posX, int posY,int ancho, int alto,std::string imagePath, Widget* parent, int damage)
:Widget(posX, posY,imagePath, parent) {

	//Se hace resize al tamaño establecido en el constructor
	SDL_Surface* resizedSurface = SurfaceUtils::resizeSurface(ancho,alto,surface);

	SDL_FreeSurface(surface);

	surface = resizedSurface;

	initWidgetMovimiento(type, damage, posX, posY);
}

bool WidgetMovimiento::isDrawable() {
	return drawable;
}

WidgetMovimiento::~WidgetMovimiento() {
	if(surfaceShown != NULL){
		SDL_FreeSurface(surfaceShown);
		surfaceShown = NULL;
	}
}


int WidgetMovimiento::analyseBorderIntersection(borderType border,	SDL_Rect objectRect, SDL_Rect screenRect) {

	int result = -1;
	switch (border) {
	case TOP_BORDER: {
		//If the dot is going out through the top border the dot reflection will be analyzed
		if (objectRect.y < 0) {
			// If half dot is over the top border the other half will be reflected on the bottom border
			if (objectRect.y + objectRect.h > 0)
				result = 1;
			else
				result = 0;
		}
	}
	break;
	case BOTTOM_BORDER: {
		//If the dot is going out through the bottom border the dot reflection will be analyzed
		if (objectRect.y + objectRect.h > screenRect.h) {
			// If half dot is over the bottom border the other half will be reflected on the top border
			if (objectRect.y < screenRect.h)
				result = 1;
			else
				result = 0;
		}
	}
	break;
	case RIGHT_BORDER: {
		//If the dot is going out through the right border the dot reflection will be analyzed
		if (objectRect.x + objectRect.w > screenRect.w) {
			// If half dot is over the right border the other half will be reflected on the left border
			if (objectRect.x < screenRect.w)
				result = 1;
			else
				result = 0;
		}
	}
	break;
	case LEFT_BORDER: {
		//If the dot is going out through the left border the dot reflection will be analyzed
		if (objectRect.x < 0) {
			// If half dot is over the left border the other half will be reflected on the right border
			if (objectRect.x + objectRect.w > 0)
				result = 1;
			else
				result = 0;
		}
	}
	break;
	}
	return result;
}

void WidgetMovimiento::positionCorrection() {
	SDL_Rect screenRect;
	screenRect.h =((PlayScenarioPanel*)parent)->getHeight();
	screenRect.w = ((PlayScenarioPanel*)parent)->getWidth();

	//Se obtiene el SDL_Rect ajustado a la próxima posicion que tendra el objeto
	SDL_Rect objectNextRect = getNextRectangle();

	//Se analiza si la proxima posicion del objeto estará enteramente fuera del area de la pantalla
	int topBorderIntersection = analyseBorderIntersection(TOP_BORDER, objectNextRect, screenRect);
	int bottomBorderIntersection = analyseBorderIntersection(BOTTOM_BORDER, objectNextRect, screenRect);
	int leftBorderIntersection = analyseBorderIntersection(LEFT_BORDER,	objectNextRect, screenRect);
	int rightBorderIntersection = analyseBorderIntersection(RIGHT_BORDER, objectNextRect, screenRect);

	SDL_Rect rectangle = parcel->getRectangle();

	//Si salio enteramente el objeto de la pantalla se movera al lado opuesto por donde se dirigia
	if (leftBorderIntersection == 0) {
		nextX += screenRect.w + rectangle.w;
	}
	if (rightBorderIntersection == 0) {
		nextX -= screenRect.w + rectangle.w;
	}
	if (topBorderIntersection == 0) {
		nextY += screenRect.h + rectangle.h;
	}
	if (bottomBorderIntersection == 0) {
		nextY -= screenRect.h + rectangle.h;
	}

}

void WidgetMovimiento::showOn(SDL_Surface* canvasSurface) {

	// Position where the surface will be draw
	SDL_Rect posRect = this->getRectangle();

	//Show the original dot
	SDL_BlitSurface(surfaceShown, NULL, canvasSurface, &posRect);
}

bool WidgetMovimiento::checkProjectedCollision(WidgetMovimiento* widgetMov){

	//Si alguno esta muerto no hay colision
	if(!this->drawable || !widgetMov->drawable ) return false;

	bool collisionFlag = false;

	//Obtengo posiciones iniciales y finales del objeto A local
	SDL_Rect rectangle = parcel->getRectangle();
	int objPosX = rectangle.x;
	int objPosY = rectangle.y;
	int objNextPosX = this->nextX;
	int objNextPosY = this->nextY;

	//Obtengo un cuadrado que se forma con los 2 puntos como extremos para el objeto A
	int rectAX = Minimum(objPosX, objNextPosX);
	int rectAY = Minimum(objPosY, objNextPosY);
	int rectAWidth = abs(Maximum(objPosX, objNextPosX) - rectAX);
	int rectAHeight = abs(Maximum(objPosY, objNextPosY) - rectAY);

	SDL_Rect posRectAStrechced;
	posRectAStrechced.x = rectAX;
	posRectAStrechced.y = rectAY;
	posRectAStrechced.w = rectAWidth;
	posRectAStrechced.h = rectAHeight;

	SDL_Rect posRectB = widgetMov->getNextRectangle();

	//Verifico si los cuadrados formados por A y B proyectados se intersecan
	//Obtengo el cuadrado que resulta de la interseccion
	SDL_Rect collisionRect = SurfaceUtils::intersectionRect(posRectAStrechced, posRectB);

	//Verificación si el rectangulo de colision es no nulo
	if(collisionRect.w >= COLISION_RECTANGLE_WINDOW && collisionRect.h >= COLISION_RECTANGLE_WINDOW)
	{
		SDL_Rect normalA;
		normalA.x = collisionRect.x - posRectAStrechced.x;
		normalA.y = collisionRect.y - posRectAStrechced.y;
		SDL_Rect normalB = widgetMov->NormalizeBounds(collisionRect);

		//Se crea una linea que dibuja la trayectoria del objeto
		SDL_Surface* lineSurface = SDL_CreateRGBSurface(SDL_SWSURFACE, posRectAStrechced.w,posRectAStrechced.h, 32, 0, 0, 0, 0);
		lineColor(lineSurface,(objPosX - posRectAStrechced.x),(objPosY - posRectAStrechced.y),(objNextPosX - posRectAStrechced.x),(objNextPosY - posRectAStrechced.y),SDL_MapRGB(this->surface->format, 255, 255, 255));

		SDL_Surface* surfaceWithAux = SurfaceUtils::rotateSurface(widgetMov->surface,widgetMov->getAngle());
		collisionFlag = checkColisionByPixel(collisionRect, lineSurface, normalA, surfaceWithAux, normalB);

	    //Free intermediate surface
		SDL_FreeSurface(lineSurface);
	    SDL_FreeSurface(surfaceWithAux);

	}
	return collisionFlag;
}

bool WidgetMovimiento::checkColisionByPixel(SDL_Rect collisionRect, SDL_Surface *surfaceAux, SDL_Rect normalA, SDL_Surface *surfaceWithAux, SDL_Rect normalB)
{
    Uint32 bgColor = SurfaceUtils::getTransparentColor();
    for(int y = 0; y < collisionRect.h ; y+=COLISION_WINDOW){
		for(int x = 0; x < collisionRect.w ; x+=COLISION_WINDOW){
			//En caso del pixel sea distinto del color de fondo en ambas imagenes significa que hay colision
			if(SurfaceUtils::get_pixel32(surfaceAux, normalA.x + x, normalA.y + y) != bgColor &&
					SurfaceUtils::get_pixel32(surfaceWithAux, normalB.x + x, normalB.y + y) != bgColor){
				return true;

			}
		}
	}

    return false;
}

bool WidgetMovimiento::checkCollision(WidgetMovimiento* widgetMov){

	//Si alguno esta muerto no hay colision
	if(!this->drawable || !widgetMov->drawable ) return false;

	//Checkeo de excepciones en colisiones
	if(this->getType() == EXPLOSION || widgetMov->getType() == EXPLOSION) return false;

	if(this->getType() == BONUS && widgetMov->getType() == BONUS) return false;
	if(this->getType() == BONUS && widgetMov->getType() == AMMO) return false;
	if(this->getType() == BONUS && widgetMov->getType() == ASTEROID) return false;
	if(this->getType() == BONUS && widgetMov->getType() == SHIP_BASE) return false;

	if(this->getType() == SHIP_BASE && widgetMov->getType() == SHIP_BASE) return false;
	if(this->getType() == SHIP_BASE && widgetMov->getType() == ASTEROID) return false;
	if(this->getType() == SHIP_BASE && widgetMov->getType() == BONUS) return false;

	if(this->getType() == AMMO && widgetMov->getType() == AMMO) return false;
	if(this->getType() == AMMO && widgetMov->getType() == BONUS) return false;

	if(this->getType() == ASTEROID && widgetMov->getType() == ASTEROID) return false;
	if(this->getType() == ASTEROID && widgetMov->getType() == BONUS) return false;
	if(this->getType() == ASTEROID && widgetMov->getType() == SHIP_BASE) return false;

	//Ammo Mina logic
	if( (this->getType() == AMMO && ((Ammo*)this)->getAmmoType() == AMMO_MINA)
			&& widgetMov->getType() == SHIP_BASE) return false;
	if( (this->getType() == AMMO && ((Ammo*)this)->getAmmoType() == AMMO_MINA)
				&& widgetMov->getType() == ASTEROID) return false;

	if( this->getType() == SHIP_BASE  &&
			(widgetMov->getType() == AMMO && ((Ammo*)widgetMov)->getAmmoType() == AMMO_MINA)) return false;
	if( this->getType() == ASTEROID  &&
			(widgetMov->getType() == AMMO && ((Ammo*)widgetMov)->getAmmoType() == AMMO_MINA)) return false;
	//END Ammo Mina logic


	//Fin de checkeo de excepciones de colisiones

	bool collisionFlag = false;
	SDL_Rect rectangle = parcel->getRectangle();
	int deltaX = fabs(this->nextX - rectangle.x);
	int deltaY = fabs(this->nextY - rectangle.y);
	int screenWidth = ((PlayScenarioPanel*)parent)->getWidth();
	int screenHeight = ((PlayScenarioPanel*)parent)->getHeight();
	//Se verifica si la distancia de la proxima posicion del objeto supera su ancho y/o alto de la imagen
	//Se hara el checkeo de colision proyectada solo para los casos que la imagen haga saltos entre frames debido a su alta velocidad

	if(this->getType() == AMMO && (deltaX < screenWidth - DELTA_PROYECTEDMIN) && (deltaY < screenHeight - DELTA_PROYECTEDMIN)){
		collisionFlag = checkProjectedCollision(widgetMov);
	}else{
		//Proximas posiciones de los objetos
		SDL_Rect posNextRectA = this->getNextRectangle();
		SDL_Rect posNextRectB = widgetMov->getNextRectangle();

		//Verifico si los cuadrados formados por A y B se intersecan
		//Obtengo el cuadrado que resulta de la interseccion
		SDL_Rect collisionRect = SurfaceUtils::intersectionRect(posNextRectA, posNextRectB);

		//Verificación si el rectangulo de colision es no nulo
		if(collisionRect.w != 0 && collisionRect.h != 0)
		{
			SDL_Rect normalA = this->NormalizeBounds(collisionRect);
			SDL_Rect normalB = widgetMov->NormalizeBounds(collisionRect);

			//Se recorren las imagenes pixel a pixel y se evalua si en la pos (X,Y) es distinta del color de fondo

			//Create the intermediate colision image
			SDL_Surface * surfaceAux = SurfaceUtils::rotateSurface(this->surface,this->angle);
			SDL_Surface * surfaceWithAux = SurfaceUtils::rotateSurface(widgetMov->surface,widgetMov->getAngle());
			collisionFlag = checkColisionByPixel(collisionRect, surfaceAux, normalA, surfaceWithAux, normalB);

		    //Free intermediate surface
		    SDL_FreeSurface(surfaceAux);
		    SDL_FreeSurface(surfaceWithAux);
		}
	}
	return collisionFlag;
}

SDL_Rect WidgetMovimiento::getRectangle(){
	// Position where the surface will be draw
	SDL_Rect rectangle = parcel->getRectangle();
	SDL_Rect posRect;
	posRect.x = rectangle.x - (this->surfaceShown->w) / 2;
	posRect.y = rectangle.y - (this->surfaceShown->h) / 2;
	posRect.w = this->surfaceShown->w;
	posRect.h = this->surfaceShown->h;

	return posRect;
}

SDL_Rect WidgetMovimiento::getNextRectangle(){
	// Position where the surface will be draw
	SDL_Rect posRect;
	posRect.x = nextX - (this->surfaceShown->w) / 2;
	posRect.y = nextY - (this->surfaceShown->h) / 2;
	posRect.w = this->surfaceShown->w;
	posRect.h = this->surfaceShown->h;

	return posRect;
}


SDL_Rect WidgetMovimiento::NormalizeBounds(SDL_Rect rect){
	SDL_Rect rectOrig = this->getNextRectangle();
	SDL_Rect rectResult;
	rectResult.x = rect.x - rectOrig.x;
	rectResult.y = rect.y - rectOrig.y;
	rectResult.w = rect.w;
	rectResult.h = rect.h;

	return rectResult;
}


void WidgetMovimiento::prepareSurfaceShown() {
	//Se limpia la superficie
	if (surfaceShown != NULL) {
		SDL_FreeSurface(surfaceShown);
		surfaceShown = NULL;
	}
	//Se rota la superficie original a la orientación actual para ser dibujada
	if (surface != NULL) {
		surfaceShown = SurfaceUtils::rotateSurface(surface,angle);
		this->parcel->updateHW(surfaceShown->h,surfaceShown->w);
	}
}

void WidgetMovimiento::resolve(){

	//Corrección de la posición en caso que el widgetMovimiento se pase de los limites de la pantalla
	//En caso positivo se reposicionara al lado opuesto por donde salio
	positionCorrection();

	//Se limpia la superficie
	prepareSurfaceShown();
}

int WidgetMovimiento::getDamage() const
{
    return damage;
}


playElement WidgetMovimiento::getType() const
{
    return type;
}

int WidgetMovimiento::getAngle() const
{
    return angle;
}

void WidgetMovimiento::initColorKey()
{
	if(surface != NULL){
	    //Le cambio el fondo de color
	    Uint32 colorkey;
	    colorkey = SurfaceUtils::getTransparentColor();
	    SDL_SetColorKey( surface, SDL_SRCCOLORKEY, colorkey );
	}
}

void WidgetMovimiento::updateNextPosition(int nextX,int nextY){
	this->nextX = nextX;
	this->nextY =  nextY;
	parcel->updateXY(nextX,nextY);
}

void WidgetMovimiento::updateNextAngle(int nextAngle){
	this->nextAngle = nextX;
}
